Динамическая загрузка DLL с Mono-классами
Общие вопросы о Unity3D
Ответить
Сообщений: 15 • Страница 1 из 1
Динамическая загрузка DLL с Mono-классами
Air72 05 янв 2020, 10:54
Доброго времени суток!Занадобилось мне в проект добавлять новый контент не компилируя основную программу. (этакие аддоны делать)Немного изучив "AssetBundle", понял, что C# скрипты в него не загрузишь, да и многие советуют этого не делать, хз, а что такого то?...Тогда я схитрил и создал txt файл своего скрипта и прикрепил к "AssetBundle" вместе с 3d моделью.Подгрузил его не самым хорошим методом, но всё же удачно.Не ругайте сильно за код, я его 15ч из разных источников собирал.
Синтаксис:Синтаксис: [ Показать ]Используется csharp
IEnumerator Start()
{
var bundleLoadRequest = AssetBundle.LoadFromFileAsync(Application.dataPath+"/DLC/aLucyDLC");
yield return bundleLoadRequest;
var myLoadedAssetBundle = bundleLoadRequest.assetBundle;
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
yield break;
}
var assetLoadRequest = myLoadedAssetBundle.LoadAssetAsync("aLucy");
var assetLoadRequest1 = myLoadedAssetBundle.LoadAssetAsync("SwitchColor");
yield return assetLoadRequest;
yield return assetLoadRequest1;
var txt = assetLoadRequest1.asset as TextAsset;
string source = txt.ToString();
Dictionary providerOptions = new Dictionary
{
{"CompilerVersion", "v2.0"}
};
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};
compilerParams.ReferencedAssemblies.Add("C:\\Program Files\\Unity\\Editor\\Data\\Managed\\UnityEngine.dll");
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
if (results.Errors.Count != 0)
{
Debug.Log(results.Errors[0].ErrorText);
throw new Exception("Mission failed!");
}
var assembly = results.CompiledAssembly;
var type = assembly.GetType("SwitchColor");
GameObject prefab = assetLoadRequest.asset as GameObject;
GameObject go = Instantiate(prefab, new Vector3(0,0,0), Quaternion.Euler(new Vector3(0,0,180)));
go.AddComponent(type);
myLoadedAssetBundle.Unload(false);
}
"aLucyDLC" это наш "AssetBundle""aLucy" это префаб модели со скриптом (просто меняющий основной цвет)"SwitchColor" собственно само имя классаВ строке, где я тупо указываю прямой путь к ДЛЛ UnityEngine, нужно для компиляции скрипта из-за того, что мой класс наследник MonoBehaviour.В результате я получаю загруженную 3д модель со скриптом на ней. И при этом всё робит.Только беда в том, что на модели два(!) скрипта. Старый, который был там изначально и новый который добавил.(это вполне нормально)У меня пара-тройка вопросов.1. Мой новый загруженный скрипт "SwitchColor" не видит вся остальная программа. Потому, что он находится в переменной "results". Как мне загрузить скомпилированный код в общую память программы? Что бы можно было использовать класс "SwitchColor" в других скриптах.2. На сколько я понимаю "System.Reflection.Assembly.Load()" мог бы помочь решить эту проблему, только я не соображу из какого места мне загрузить мою сборку?... Скомпилированная находится тут "results.CompiledAssembly", как то в "FileStream" переводить и грузить из потока?
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Jarico 05 янв 2020, 13:51
https://docs.unity3d.com/ru/current/Manual/scriptsinassetbundles.html
Github: _https://github.com/redheadgektor
Discord: Конь! Чаю!#9382 (сижу редко)YouTube: _https://www.youtube.com/channel/UCPQ04Xpbbw2uGc1gsZtO3HQ
Telegram: _https:///redheadgektor
Jarico
Адепт
Сообщения: 1069Зарегистрирован: 06 янв 2019, 17:37Откуда: 0xDEAD Skype: none
Сайт
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Air72 05 янв 2020, 14:05
Вот как раз из этого примера я и взял часть кода.Почему не весь? - потому, что у меня не DLL-сборка загружена в "TextAsset txt = bundle.Load("myBinaryAsText", typeof(TextAsset)) as TextAsset;"Как бы я свой скрипт скомпилировал в ДЛЛ в памяти. Но, правильно ли я понимаю, что полученная мной сборка не может быть загружена в общую память приложения?
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
1max1 05 янв 2020, 14:28
1. Мой новый загруженный скрипт "SwitchColor" не видит вся остальная программа. Потому, что он находится в переменной "results". Как мне загрузить скомпилированный код в общую память программы? Что бы можно было использовать класс "SwitchColor" в других скриптах.Объяви глобальную статическую переменную своего типа. Но конкретно класс SwitchColor напрямую использовать не получится, то есть вот так:Синтаксис:Синтаксис: [ Показать ]Используется csharpSwitchColor sc = go.GetComponent(); написать ты не сможешь, зато вот так сможешь:Синтаксис:Синтаксис: [ Показать ]Используется csharpobject sc = go.GetComponent(type);
object v1 = type.GetField("v1").GetValue(sc);То есть возится с рефлексией придется по любому. Всё-таки лучше не класс подгружать, а сборку, чтобы не возится со всякими провайдерами.
1max1
Адепт
Сообщения: 5298Зарегистрирован: 28 июн 2017, 10:51
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Air72 05 янв 2020, 14:45
Хм.. В целом понятно, разрабы не стали развивать этот метод, возможно из-за безопасности кода и т.д. и т.п.Я почему с ДЛЛ давно не стал работать, мне не понравились задержки, когда пишешь код, Моно будто подгружает каждый раз классы. А с открытым кодом такого не было.Ну ладно, будем разбираться с DLL. Надеюсь там лучше реализация.Спасибо за ответы:)
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
1max1 05 янв 2020, 15:56
Какие еще задержки, ты о чем?
1max1
Адепт
Сообщения: 5298Зарегистрирован: 28 июн 2017, 10:51
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Air72 05 янв 2020, 16:31
1max1 писал(а):Какие еще задержки, ты о чем?В Моно, когда например MyClass.Param, дак вот когда точку ставишь, он думает, типа как бы загружает инфу о классе, что бы отобразить список доступных методов и т.д.Народ, хэлп!Вот что не так делаю?...
Синтаксис:Синтаксис: [ Показать ]Используется csharp
var bundleLoadRequest = AssetBundle.LoadFromFileAsync(Application.dataPath+"/DLC/dll");
yield return bundleLoadRequest;
var myLoadedAssetBundle = bundleLoadRequest.assetBundle;
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
yield break;
}
// Первый способ
//var assetLoadRequest1 = myLoadedAssetBundle.LoadAssetAsync("FirstDLL");
//yield return assetLoadRequest1;
//TextAsset txt = assetLoadRequest1.asset as TextAsset;
// Второй способ
TextAsset txt = myLoadedAssetBundle.LoadAsset("FirstDLL", typeof(TextAsset)) as TextAsset;
Debug.Log(txt.name); // name = SwitchColor
Debug.Log(txt.bytes.Length); // Length = 0
var assembly = System.Reflection.Assembly.Load(txt.bytes); // Error BadImageFormatException
var type = assembly.GetType("SwitchColor");
Имя класса есть, даже путь могу поглядеть "Assets\FirstDLL.dll"А почему собственно байт нет?...Упаковываю так
Синтаксис:Синтаксис: [ Показать ]Используется csharp
#if UNITY_EDITOR
using UnityEditor;
public class CreateAssetBundles
{
[MenuItem ("Assets/Build AssetBundles")]
static void BuildAllAssetBundles ()
{
BuildPipeline.BuildAssetBundles("AssetBundles", BuildAssetBundleOptions.None, BuildTarget.StandaloneOSXUniversal);
}
}
#endif
И кстати, если одну ДЛЛ упаковывать, то он вообще ничего не делает. По ходу он только имя классов сохраняет, а чего с кодом длл не так?Создал под 3.5 framework. Юнити сам сказал, что 3.5 только понимает, мол выше нельзя.__________P.S.Дал имя своей длл "FirstDLL.bytes"В итоге " var assembly = System.Reflection.Assembly.Load(txt.bytes);" принял.Length = 4608Debug.Log(assembly.GetExportedTypes()[0]); возвращает "FirstDLL.SwitchColor"А вот "var type = assembly.GetType("SwitchColor");" Не может получить класс, типа его там нет, хах)))) Что не так))_______Додумал.var type = assembly.GetType("FirstDLL.SwitchColor"); если записать так, то работает.Но работает так же как и мой первый вариант в первом посту, блин, только возни больше, что-то не так.
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
1max1 05 янв 2020, 17:31
В Моно, когда например MyClass.Param, дак вот когда точку ставишь, он думает, типа как бы загружает инфу о классе, что бы отобразить список доступных методов и т.д.Хм, у меня нет таких проблем... Хотя у меня VS...
1max1
Адепт
Сообщения: 5298Зарегистрирован: 28 июн 2017, 10:51
Вернуться к началу
[Решено] Динамическая загрузка DLL с Mono-классами
Air72 06 янв 2020, 00:29
Решение найдено, хоть и не панацея, но работает. Буду ещё тестировать на более сложных алгоритмах.Выложу решение, может кому пригодится.1. Создание AssetBundle"Первый" проект, там где мы разрабатываем дополнение к нашему "Основному" проекту, сделал так:Есть 3д модель и есть скрипт, который нам необходимо использовать на нём.Компилируем наш скрипт любым доступным средством в DLL.Переносим полученную DLL в проект.Создаём её копию и меняем расширение на "bytes" (честно хз почему так, работает и ладно)Текстовый скрипт можно убрать в резерв из проекта.Создаём префаб нашей 3д модели и переносим нужный класс из реальной DLL на него.Примечание: Если перенести текстовый скрипт, то после загрузки плагина скрипт не присвоится.Далее, выделяем наш префаб и копию DLL-ки с расширением "bytes" в окне "Project".Ставим маркер AssetBundle, например по имени 3д модели. (или как там эта метка называется)Экспортируем наш "AssetBundle" с помощью скрипта "CreateAssetBundles" (что указывал выше)На выходе получим наш плагин. Который после загрузим. Здесь всё.2. Алгоритм загрузки плагина (AssetBundle)Для теста я в сам проект переношу файл плагина.
Синтаксис:Синтаксис: [ Показать ]Используется csharp
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
// Где-то в программе... например в Start или по тригеру в Update
// С этого начинается загрузка из плагина
this.Invoke("LoadDLLs", 0.1f); // Специально для теста зависания.
_nextLoadDLC(); // Если запускать эти методы в родительском потоке, то получим зависание программы.
// Вообще можно было обойтись без этого.
// И в конец метода LoadDLLs() добавить LoadObjects();
// Проверял, работает, но вдруг dll не успеет по каким либо причинам загрузиться? По этому решил сделать так.
private void _nextLoadDLC()
{
if (_isDLLLoaded)
{
LoadObjects();
}
else
{
this.Invoke("_nextLoadDLC", 0.5f);
}
}
private bool _isDLLLoaded = false;
void LoadDLLs()
{
AssetBundle loadedDLC = AssetBundle.LoadFromFileAsync(Application.dataPath+"/DLC/aLucyDLC").assetBundle;
if (loadedDLC == null)
{
Debug.LogError("Не удалось загрузить плагин");
Debug.Break();
}
AssetBundleRequest loadDLL = loadedDLC.LoadAssetAsync("FirstDLL");
TextAsset DLLAsset = (TextAsset)loadDLL.asset;
Debug.Log(DLLAsset.bytes.Length);
// Эти методы оказались эквивалентны.
Assembly assembly = AppDomain.CurrentDomain.Load(DLLAsset.bytes);
//Assembly assembly = System.Reflection.Assembly.Load(DLLAsset.bytes);
// Этот код чисто для отладки, поглядеть, загрузился ли класс.
Type getClass = assembly.GetType("FirstDLL.SwitchColor");
Debug.Log(getClass.FullName);
loadedDLC.Unload(false);
_isDLLLoaded = true;
}
void LoadObjects()
{
AssetBundle loadedDLC = AssetBundle.LoadFromFileAsync(Application.dataPath+"/DLC/aLucyDLC").assetBundle;
if (loadedDLC == null)
{
Debug.LogError("Не удалось загрузить плагин");
Debug.Break();
}
AssetBundleRequest loadGO = loadedDLC.LoadAssetAsync("aLucy");
GameObject prefab = (GameObject)loadGO.asset;
Debug.Log(prefab.name);
Instantiate(prefab, new Vector3(0,0,0), Quaternion.Euler(new Vector3(0,0,180)));
loadedDLC.Unload(false);
}
3. Применение нашего нового класса на уже существующих объектахСкажем так. Это не дело рыться в куче загруженных сборках и искать свою... Извините, просто хотелось по быстрее проверить получится ли его использовать.
Синтаксис:Синтаксис: [ Показать ]Используется csharp
Assembly[] assembly = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in assembly)
{
if (a.GetName().Name == "FirstDLL")
{
var type = a.GetType("FirstDLL.SwitchColor");
GO.AddComponent(type);
}
}
Результат выполнения.+ Загруженная из плагина 3д модель появилась без ошибок с работающим скриптом.+ Прикреплённый новый класс корректно начал функционировать на уже существующем объекте.- Как найти свой класс более оптимально, я пока не решил.Да, можно создать глобальный массив со всеми новыми классами и ссылками на них, что бы потом выбирать их от туда, ну как вариант.
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Summersay415 06 янв 2020, 07:57
А public значения с помощью такого способа сохраняются?
Summersay415
UNец
Сообщения: 19Зарегистрирован: 01 янв 2020, 13:39Откуда: Бийск
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Air72 06 янв 2020, 10:56
В целом да, сохраняются.Не сохранился почему то собственный класс. Точнее класс остался, только сериализация слетела. Может можно её как то программно включить.Но, из-за того, что сериализация отключилась, потерялись и данные введённые через инспектор.offtopА у меня сотни таких классов... вот попадос=))Вот что тестировалось.
Синтаксис:Синтаксис: [ Показать ]Используется csharp
public enum Air72EnumTest
{
None = 0,
Test1 = 1,
Test2 =2
}
[System.Serializable]
public class Air72ClassTest
{
public string Name;
public int Index;
public Air72ClassTest()
{
}
}
public float FloatTest = 0.83676f;
public int IntTest = 45;
public bool BoolTest = true;
public string StringTest = "ТестКириллицы";
public Vector3 V3Test = new Vector3(9.31f, 3, 0.183f);
public Air72EnumTest EnumTest = Air72EnumTest.Test2;
public Air72ClassTest ClassTest = new Air72ClassTest();
Примеры на картинках.На дефолтных параметрахСкрытый текст: ![Изображение](https://sun9-41.userapi.com/c200616/v200616601/2e377/ecY6vYSllsw.jpg) ![Изображение](https://sun9-12.userapi.com/c200616/v200616601/2e37f/0vFP78VP_t0.jpg) На изменённых параметрах.Скрытый текст: ![Изображение](https://sun9-34.userapi.com/c200616/v200616601/2e38f/W95tXw4PUyo.jpg) ![Изображение](https://sun9-53.userapi.com/c200616/v200616601/2e398/SrFs4bsyz1M.jpg) P.S.AudioClip тоже отлично передался и загрузился без проблем.
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Класс для загрузки AssetBundle
Air72 06 янв 2020, 15:24
Организовал отдельный класс, который можно скомпилировать в длл и легко добавлять в проекты.Не стал пока делать делегаты, а так можно и прогресс загрузки показывать.Удобно, если контента много.
Синтаксис:Синтаксис: [ Показать ]Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
using System.Reflection;
///
/// Класс загрузки ресурсов из плагинов
///
public class DLCLoader {
private AssetBundle _loadedDLC;
///
/// Инициализация и загрузка файла плагина
///
/// Путь к плагину
public DLCLoader(string WayPlugin)
{
_loadedDLC = AssetBundle.LoadFromFileAsync(WayPlugin).assetBundle;
if (_loadedDLC == null)
{
Debug.LogError("Не удалось загрузить плагин: "+Path.GetFileName(WayPlugin));
Debug.Break();
}
}
///
/// Загружает DLL сборку
///
/// Имя файла в плагине
public Assembly LoadDLL(string NameAsset)
{
if (_loadedDLC == null) return null;
AssetBundleRequest loadDLL = _loadedDLC.LoadAssetAsync(NameAsset);
Assembly assembly = AppDomain.CurrentDomain.Load(((TextAsset)loadDLL.asset).bytes);
//Assembly assembly = System.Reflection.Assembly.Load(DLLAsset.bytes);
return assembly;
}
///
/// Загружает объект сцены
///
/// Имя файла в плагине
public GameObject LoadGO(string NameAsset)
{
if (_loadedDLC == null) return null;
AssetBundleRequest loadGO = _loadedDLC.LoadAssetAsync(NameAsset);
return (GameObject)loadGO.asset;
}
///
/// Загружает аудио файл
///
/// Имя файла в плагине
public AudioClip LoadAudio(string NameAsset)
{
if (_loadedDLC == null) return null;
AssetBundleRequest loadGO = _loadedDLC.LoadAssetAsync(NameAsset);
return (AudioClip)loadGO.asset;
}
///
/// Загружает текстуру
///
/// Имя файла в плагине
public Texture2D LoadTexture(string NameAsset)
{
if (_loadedDLC == null) return null;
AssetBundleRequest loadGO = _loadedDLC.LoadAssetAsync(NameAsset);
return (Texture2D)loadGO.asset;
}
///
/// Получить указатель на класс
///
/// Класс
/// Имя DLL
/// Имя класса
public Type GetClass(string NameDLL, string ClassName)
{
Assembly[] assembly = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in assembly)
{
if (a.GetName().Name == NameDLL)
{
return a.GetType(NameDLL+"."+ClassName);
}
}
return null;
}
///
/// Освобождает память от загруженного плагина и уничтожает всё, что было из него добавлено
///
public void Destroy()
{
_loadedDLC.Unload(true);
}
///
/// Освобождает память только от загруженного плагина, добавленные ресурсы сохраняются
///
public void UnloadDLC()
{
_loadedDLC.Unload(false);
}
}
Использование.
Синтаксис:Синтаксис: [ Показать ]Используется csharp
public DLCLoader DLCl;
public GameObject GO; // для теста
public AudioSource AS; // для теста
void Start()
{
DLCl = new DLCLoader(Application.dataPath+"/DLC/aLucyDLC"); // Подключаем наш плагин.
DLCl.LoadDLL("FirstDLL"); // В первую очередь сразу же грузим классы
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// Загрузка и создание 3д объекта.
Instantiate(DLCl.LoadGO("aLucy"), new Vector3(0,0,0), Quaternion.Euler(new Vector3(0,0,180)));
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
// Получение указателя на наш класс и добавление его на уже существующий объект
GO.AddComponent(DLCl.GetClass("FirstDLL", "SwitchColor"));
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
// Загрузка аудио файла в AudioSource
AS.clip = DLCl.LoadAudio("Sound_02025");
}
if (Input.GetKeyDown(KeyCode.P))
{
// Для теста воспроизведения
AS.Play();
}
Air72
UNец
Сообщения: 39Зарегистрирован: 02 май 2014, 10:15
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
AngryCat 06 янв 2020, 23:45
Странно, но я не могу включить dll библиотеку в сборку бандла. Пишет ошибку Unrecognized assets cannot be included in AssetBundles: "Assets/Scripts/Arc/Arc.dll".
Здесь могла бы быть ваша реклама.
AngryCat
Старожил
Сообщения: 713Зарегистрирован: 20 июл 2018, 22:29 Skype: Дискорд - Флеш#4099
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
1max1 06 янв 2020, 23:55
Расширение делай .bytes
1max1
Адепт
Сообщения: 5298Зарегистрирован: 28 июн 2017, 10:51
Вернуться к началу
Re: Динамическая загрузка DLL с Mono-классами
Jarico 07 янв 2020, 13:10
AngryCat писал(а):Странно, но я не могу включить dll библиотеку в сборку бандла. Пишет ошибку Unrecognized assets cannot be included in AssetBundles: "Assets/Scripts/Arc/Arc.dll".Можно расположить DLL на хосте, и скачать с помощью UnityWebRequest и держать в памяти до загрузки бандла
Синтаксис:Синтаксис: [ Показать ]Используется csharp
byte[] rawAssembly = null;// байты DLL-ки с классами
Assembly assembly = Assembly.Load(rawAssembly); //делаем загрузку
//ручной вызов метода если нужно
Type type = assembly.GetType("Namespace.Class");//получаем типы по пространству имён и классу
object obj = Activator.CreateInstance(type); //создаём экземпляр класса
MethodInfo method = type.GetMethod("method_1", BindingFlags.Static | BindingFlags.Public);//получаем метод в классе который нужно вызвать (к сожалению только статические)
method.Invoke(obj, null); //запускаем метод (null - аргументы)
Важно это делать перед загрузкой бандла в память, если сделать наоборот то двиг не найдёт необходимые классы для бандла и все загруженные ресурсы будут иметь Missing Reference
Github: _https://github.com/redheadgektor
Discord: Конь! Чаю!#9382 (сижу редко)YouTube: _https://www.youtube.com/channel/UCPQ04Xpbbw2uGc1gsZtO3HQ
Telegram: _https:///redheadgektor
Jarico
Адепт
Сообщения: 1069Зарегистрирован: 06 янв 2019, 17:37Откуда: 0xDEAD Skype: none
Сайт
Вернуться к началу
Ответить
Сообщений: 15 • Страница 1 из 1
Вернуться в Общие вопросы
Кто сейчас на конференции
Сейчас этот форум просматривают: Google [Bot] и гости: 9
|